module nazo.args;
/**
 * mainの引数を扱う
 * History:
 *          0.0.0.1 バージョン付け開始
 * Authors: Nazo
 * Version: 0.0.0.1
 * License: Public Domain
 */
import std.conv;

template TOption(T){
  ///コンストラクタ
  this(string longer,T* prop){
    _tmp("",longer,prop);
  }
  ///コンストラクタ
  this(string shorter,string longer,T* prop){
    _tmp(shorter,longer,prop);
  }
  //dmdのmixinのバグ避け。thisの扱いが変?
  protected void _tmp(string shorter,string longer,T* prop){
    this.shorter=shorter;
    this.longer=longer;
    this.prop=prop;
  }
  ///プロパティへのポインタ
  T* prop;
}

///オプション
class Option{
  ///-*。省略可。
  string shorter;
  ///--*。省略不可。
  string longer;
}

///フラグオプション
class FlagOption:Option{
  mixin TOption!(bool);
  //バグ回避
  this(string longer,bool* prop){
    _tmp("",longer,prop);
  }
  this(string shorter,string longer,bool* prop){
    _tmp(shorter,longer,prop);
  }
  this(string shorter,string longer,bool* prop,bool set){
    _tmp(shorter,longer,prop);
    this.set=set;
  }
  ///もし指定されていたらセットする値
  bool set=true;
}

class ArgsOption:Option{}
///文字列のオプション
class StringOption:ArgsOption{
  mixin TOption!(string);
}
///数値のオプション
class UintOption:ArgsOption{
  mixin TOption!(uint);
}

///コマンドラインオプションを扱う
class Args{
  private string[] args;
  private FlagOption[] fopts;
  private ArgsOption[] aopts;
  ///オプションをセットします。
  public void setOption(Option opt){
    if(ArgsOption t=cast(ArgsOption)opt){
      aopts~=t;
    }else if(FlagOption t=cast(FlagOption)opt){
      fopts~=t;
    }else{
      assert(0);
    }
  }
  ///コンストラクタ
  this(string[] args){
    this.args=args;
  }
  ///設定します。オプションを除いた残りを返します。
  ///Bugs: -faild -ok okval
  string[] set(){
    string[] result;
    bool isOptions=true;
    string beforeCommand;
    foreach(uint index,string _arg;args){
      auto arg=_arg;
      if(index==0)continue;
      else if(isOptions){
        bool ok=false;
        if(arg[0]=='-'&&beforeCommand!="")
          throw new Exception(beforeCommand~" is not supported.");
        foreach(FlagOption opt;fopts){
          if(arg=="-"~opt.shorter||arg=="--"~opt.longer){*opt.prop=opt.set;ok=true;break;}
        }
        if(ok)continue;
        if(arg[0]=='-')beforeCommand=arg;
        else if(beforeCommand!=""){
          bool ok2=false;
          foreach(ArgsOption opt;aopts){
            if(beforeCommand=="-"~opt.shorter||beforeCommand=="--"~opt.longer){
              if(StringOption t=cast(StringOption)opt)*t.prop=arg;
              else if(UintOption t=cast(UintOption)opt)*t.prop=.toUint(arg);
              else assert(0);
              ok2=true;break;
            }
          }
          if(!ok2)
            throw new Exception(beforeCommand~" is not supported.");
          beforeCommand="";
        }else isOptions=false;
      }
      if(!isOptions){if(arg[0]=='\\')arg=arg[1..$];result~=arg;}
    }
    if(beforeCommand!="")throw new Exception(beforeCommand~" is not supported or parameter is missing.");
    return result;
  }
}

struct TestOptions{
  string stringTest;
  uint uintTest;
  bool flagTest;
  bool flagTest2=true;
}
import std.stdio;
unittest{
  static string[] sargs=["test","-t","test","-t2","12","--flag","-s","test","test"];
  Args arg=new Args(sargs);
  TestOptions* test=new TestOptions;
  arg.setOption(new StringOption("t","test",&test.stringTest));
  arg.setOption(new UintOption("t2","test2",&test.uintTest));
  arg.setOption(new FlagOption("f","flag",&test.flagTest));
  arg.setOption(new FlagOption("s","ssss",&test.flagTest2,false));
  string[] items=arg.set();
  assert(test.stringTest=="test");
  assert(test.uintTest==12);
  assert(test.flagTest==true);
  assert(test.flagTest2==false);
  static string[] tmp=["test","test"];
  assert(items==tmp);
}
